import os
from time import time, sleep, strftime, localtime
import cv2
from random import randint
import numpy as np
import win32api
import win32gui, win32ui, win32con, win32print, win32com.client
import win32process
import pydirectinput
from threading import Thread
from pynput import keyboard

print("cnpy: 1.3版本")

# https://ps.xieniao.com/   在线ps




hwnd_map = {}




def get_all_hwnd(hwnd, mouse):
    if (win32gui.IsWindow(hwnd) and
            win32gui.IsWindowEnabled(hwnd) and
            win32gui.IsWindowVisible(hwnd)):
        hwnd_map.update({hwnd: win32gui.GetWindowText(hwnd)})


def 通过名字获取软件句柄(软件名):
    句柄list = []
    win32gui.EnumWindows(get_all_hwnd, 0)
    for h, t in hwnd_map.items():
        if t:
            if t == 软件名:
                句柄list.append(h)

    return 句柄list

def 获取模拟器操作句柄(hwnd):

    def callback(hwnd, controls):
        controls.append(hwnd)
    controls = []
    win32gui.EnumChildWindows(hwnd, callback, controls)


    return controls

    # for control in controls:
    #     print(control)

def 获取所有窗口():
    win32gui.EnumWindows(get_all_hwnd, 0)
    for h, t in hwnd_map.items():
        if t:
            print(f"{t}:{h},")


def 获取屏幕分辨率():
    try:
        hDC = win32gui.GetDC(0)
        width = win32print.GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
        height = win32print.GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)
        return 0, 0, width, height
    except Exception as e:
        处理异常("获取屏幕分辨率出错", e)

def 截图(范围=None):
    if not 范围:
        范围 = 获取屏幕分辨率()
    x, y, h1, w1 = 范围
    w = h1 - x
    h = w1 - y
    hwnd = 0  # 窗口的编号，0号表示当前活跃窗口
    # 根据窗口句柄获取窗口的设备上下文DC（Divice Context）
    hwndDC = win32gui.GetWindowDC(hwnd)
    # 根据窗口的DC获取mfcDC
    mfcDC = win32ui.CreateDCFromHandle(hwndDC)
    # mfcDC创建可兼容的DC
    saveDC = mfcDC.CreateCompatibleDC()
    # 创建bigmap准备保存图片
    saveBitMap = win32ui.CreateBitmap()
    # 为bitmap开辟空间
    saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
    # 高度saveDC，将截图保存到saveBitmap中
    saveDC.SelectObject(saveBitMap)
    # 截取从左上角（0，0）长宽为（w，h）的图片
    saveDC.BitBlt((0, 0), (w, h), mfcDC, (x, y), win32con.SRCCOPY)
    # saveBitMap.SaveBitmapFile(saveDC, filename)
    img = saveBitMap.GetBitmapBits(True)
    img = np.frombuffer(img, dtype="uint8")
    img.shape = (h, w, 4)
    # frame = cv2.cvtColor(IMG, cv2.COLOR_BGR2RGB)
    win32gui.DeleteObject(saveBitMap.GetHandle())
    mfcDC.DeleteDC()
    saveDC.DeleteDC()
    return img

def 后台截图(handle):
    """
    通过win32方式截图，并且无视硬件加速
    https://stackoverflow.com/questions/19695214/screenshot-of-inactive-window-printwindow-win32gui
    """
    # ctypes.windll.user32.SetProcessDPIAware()  # 抑制缩放
    #
    # rect = win32gui.GetWindowRect(handle)
    # width, height = rect[2] - rect[0], rect[3] - rect[1]
    #
    # hwnd_dc = win32gui.GetWindowDC(handle)
    # mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
    # save_dc = mfc_dc.CreateCompatibleDC()
    # save_bit_map = win32ui.CreateBitmap()
    #
    # save_bit_map.CreateCompatibleBitmap(mfc_dc, width, height)
    # save_dc.SelectObject(save_bit_map)
    #
    # ctypes.windll.user32.PrintWindow(handle, save_dc.GetSafeHdc(), 3)  # 如果仍然是黑屏，尝试修改数字 ‘3’ (从0开始...)
    # bmpinfo = save_bit_map.GetInfo()
    # bmpstr = save_bit_map.GetBitmapBits(True)
    #
    # capture = np.frombuffer(bmpstr, dtype=np.uint8).reshape((bmpinfo["bmHeight"], bmpinfo["bmWidth"], 4))
    # capture = np.ascontiguousarray(capture)[..., :-1]
    #
    # win32gui.DeleteObject(save_bit_map.GetHandle())
    # save_dc.DeleteDC()
    # mfc_dc.DeleteDC()
    # win32gui.ReleaseDC(handle, hwnd_dc)
    #
    # # capture = cv2.cvtColor(capture, cv2.COLOR_RGBA2RGB)
    # return capture


# 图像

def 处理异常(text, e):
    print(f"{text}: {e}")
    raise


class 图像处理类:

    def __init__(self):

        print("初始化图像处理成功")

     
    def 找图(self, 大图, 小图, 偏移=(0, 0), 相似度=0.9, 灰度=True):
        """
        :param 大图: 屏幕或软件截图
        :param 小图: 要找的图
        :param 相似度: 最低相似度
        :param 偏移: 软件先对屏幕的偏移
        :return: 找图结果或None
        """
        找图结果list = self.找多图(大图, 小图, 相似度=相似度, 偏移=偏移, 灰度=灰度)

        return 找图结果list[0] if 找图结果list else None

     
    def 找多图(self, 大图, 小图, 相似度=0.9, 偏移=(0, 0), 灰度=True):
        """
        :param 大图: 屏幕或软件截图
        :param 小图: 要找的图
        :param 相似度: 最低相似度
        :param 偏移: 软件先对屏幕的偏移
        :return: 找图结果或[]
        """
        method = cv2.TM_CCOEFF_NORMED
        if 灰度:
            s_gray = cv2.cvtColor(小图, cv2.COLOR_BGR2GRAY)
            i_gray = cv2.cvtColor(大图, cv2.COLOR_BGR2GRAY)
        else:
            s_gray = 小图
            i_gray = 大图
        res = cv2.matchTemplate(i_gray, s_gray, method)
        w, h = 小图.shape[1], 小图.shape[0]

        找图结果list = []
        while True:
            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
            top_left = max_loc
            if max_val < 相似度:
                break
            # calculator middle point
            middle_point = (top_left[0] + w / 2, top_left[1] + h / 2)

            找图结果list.append({
                "中心点": (middle_point[0] + 偏移[0], middle_point[1] + 偏移[1]),
                "矩形": (
                    top_left[0] + 偏移[0], top_left[1] + 偏移[1], top_left[0] + w + 偏移[0], top_left[1] + h + 偏移[1]),
                "相似度": max_val
            })
            cv2.floodFill(res, None, max_loc, (-1000,), max_val - 相似度 + 0.1, 1, flags=cv2.FLOODFILL_FIXED_RANGE)

        return 找图结果list

     
    def 读取图片(self, filename):
        """
        :param 路径: 图片路径
        :return: 读取后的图片
        """
        return cv2.imread(filename)
     
    def 展示图片(self, img):
        cv2.imshow('image', img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

     
    def 播放图片(self, 图片, 留存时间=1):

        cv2.imshow('image', 图片)
        return cv2.waitKey(留存时间)

     
    def 剪裁图片(self, 图片, 左上角坐标, 右下角坐标=(0, 0), 矩形大小=(0, 0)):
        """
        :param 图片: 要剪裁的图片
        :param 左上角坐标: 剪裁开始的坐标
        :param 右下角坐标: 剪裁结束的坐标
        :param 矩形大小: 剪裁图片的宽和高
        :return: 剪裁后的图片
        """
        (h, w) = 图片.shape[:2]
        (x1, y1) = (w, h)
        if len(左上角坐标) == 4:
            x1 = 左上角坐标[2]
            y1 = 左上角坐标[3]
        else:
            if 右下角坐标 != (0, 0):
                (x1, y1) = 右下角坐标
            if 矩形大小 != (0, 0):
                (x1, y1) = (左上角坐标[0] + 矩形大小[0], 左上角坐标[1] + 矩形大小[1])
        # (x1, y1) = min(max(左上角坐标[0], x1), w), min(max(左上角坐标[1], y1), h)
        return 图片[左上角坐标[1]:y1, 左上角坐标[0]:x1]


    def 标记图片(self, 图片, 左上角坐标, 右小角坐标, 颜色, 宽度=1):
        return cv2.rectangle(图片, 左上角坐标, 右小角坐标, color=颜色, thickness=宽度)

    def 转灰度图(self, 图片):
        return cv2.cvtColor(图片, cv2.COLOR_BGR2GRAY)

    def 二值化(self, 图片, 阈值, 填充色, 阈值类型=0):
        """
        :param 图片:
        :param 阈值:
        :param 填充色:
        :param 阈值类型:
        :return:
        """
        if 阈值类型 == 0:
            阈值类型 = cv2.THRESH_BINARY  # 小于阈值填 0, 大于阈值填 填充色
        elif 阈值类型 == 1:
            阈值类型 = cv2.THRESH_BINARY_INV  # 小于阈值填 填充色, 大于阈值填 0
        elif 阈值类型 == 2:
            阈值类型 = cv2.THRESH_TRUNC  # 小于阈值填 保持原色, 大于阈值填 变为灰色
        elif 阈值类型 == 3:
            阈值类型 = cv2.THRESH_TOZERO  # 小于阈值填 0, 大于阈值填 保持原色
        elif 阈值类型 == 4:
            阈值类型 = cv2.THRESH_TOZERO_INV  # 小于阈值填 保持原色, 大于阈值填 0
        ret, 图片 = cv2.threshold(图片, 阈值, 填充色, 阈值类型)
        return 图片


图像处理 = 图像处理类()

class 后台键鼠:

    def __init__(self, h):
        print("初始化后台键鼠")
        self.h = h

     
    def 点击键盘(self, 按键, 次数=1):
        pass

     
    def 按下键盘(self, 按键):
        pass

     
    def 松开键盘(self, 按键):
        pass

     
    def 按下鼠标(self, 左右="左", h=None):
        if h == None:
            h = self.h
        if 左右 == "右":
            按下 = win32con.WM_RBUTTONDOWN
            按键 = win32con.MK_RBUTTON
        else:
            按下 = win32con.WM_LBUTTONDOWN
            按键 = win32con.MK_LBUTTON

        win32api.SendMessage(h, 按下, 按键)

     
    def 松开鼠标(self, 左右="左", h=None):
        if h is None:
            h = self.h
        if 左右 == "右":
            松开 = win32con.WM_RBUTTONUP
            按键 = win32con.MK_RBUTTON
        else:
            松开 = win32con.WM_LBUTTONUP
            按键 = win32con.MK_LBUTTON

        win32api.SendMessage(h, 松开, 按键)

    # def 单击鼠标(self, 左右="左"):
    #     if 左右 == "右":
    #         res = self.objdll.M_RightUp(self.hdl)
    #     else:
    #         res = self.objdll.M_LeftUp(self.hdl)
     
    def 点击鼠标(self, x, y, 左右="左", i=1, h=None):
        print("点击鼠标")
        if h is None:
            h = self.h
        if 左右 == "右":
            按下 = win32con.WM_RBUTTONDOWN
            松开 = win32con.WM_RBUTTONUP
            按键 = win32con.MK_RBUTTON
        else:
            按下 = win32con.WM_LBUTTONDOWN
            松开 = win32con.WM_LBUTTONUP
            按键 = win32con.MK_LBUTTON

        pos = win32api.MAKELONG(x, y)
        for _ in range(i):
            win32api.PostMessage(h, 按下, 按键, pos)
            sleep(0.01)
            win32api.PostMessage(h, 松开, 按键, pos)
            sleep(0.1)

     
    def 移动鼠标(self, x, y, h=None):
        if h is None:
            h = self.h
        x = int(x)
        y = int(y)
        win32gui.SendMessage(h, win32con.WM_MOUSEMOVE, 0, (y << 16), x)

     
    def 滚动鼠标(self, 上下="下"):
        pass
        # if 上下 == "上":
        #     res = self.objdll.M_MouseWheel(self.hdl, 1)
        # else:
        #     res = self.objdll.M_MouseWheel(self.hdl, -1)

     
    def 随机坐标(self, 坐标, 范围=3):
        x = 坐标[0] + randint(-范围, 范围)
        y = 坐标[1] + randint(-范围, 范围)
        return x, y

     
    def 精准点击(self, 坐标, 左右="左", 次数=1, 高精度=False):
        if not 高精度:
            x, y = self.随机坐标(坐标)
        else:
            x = 坐标[0]
            y = 坐标[1]
        self.点击鼠标(x, y, 左右=左右, i=次数)

     
    def 范围点击(self, 范围, 左右="左", 次数=1):
        """
        :param 范围: 一个矩形的坐标
        :param 左右: 左 或者 右
        :param 次数: 鼠标点击次数
        :return: None
        """
        范围类型 = type(范围)
        if 范围类型 == tuple or 范围类型 == list:
            if len(范围) == 2:
                self.精准点击((int(范围[0]), int(范围[1])), 左右, 次数)
                return
        elif 范围类型 == dict:
            范围 = 范围["矩形"]
        try:
            x = randint(int(范围[0]), int(范围[2]))
            y = randint(int(范围[1]), int(范围[3]))
            self.点击鼠标(x, y, 左右=左右, i=次数)
        except Exception as e:
            处理异常("范围点击出错", e)

     
    def 拖动鼠标(self, 开始坐标, 结束坐标):
        """
        :param 开始坐标: 坐标或者范围
        :param 结束坐标: 坐标或者范围
        :return:
        """
        try:
            if len(开始坐标) == 4:
                开始坐标 = randint(int(开始坐标[0]), int(开始坐标[2])), randint(int(开始坐标[1]), int(开始坐标[3]))
            if len(结束坐标) == 4:
                结束坐标 = randint(int(结束坐标[0]), int(结束坐标[2])), randint(int(结束坐标[1]), int(结束坐标[3]))
            self.移动鼠标(int(开始坐标[0]), int(开始坐标[1]))
            self.按下鼠标()
            self.移动鼠标(int(结束坐标[0]), int(结束坐标[1]))
            self.松开鼠标()
        except Exception as e:
            处理异常("拖动鼠标出错", e)


# class 键鼠_Y:
#
#     def __init__(self):
#         if not sdsfdsgfdgfdg :
#             print("请先登录")
#             return
#         print("初始化键盘鼠标")
#         self.objdll = ctypes.windll.LoadLibrary('./msdk.dll')
#         self.objdll.M_Open.restype = ctypes.c_uint64
#         self.hdl = self.objdll.M_Open(1)
#         self.hdl = ctypes.c_uint64(self.hdl)
#         self.键盘编码 = {
#             "a": 4, "b": 5, "c": 6, "d": 7, "e": 8, "f": 9, "g": 10, "h": 11, "i": 12, "j": 13, "k": 14, "l": 15,
#             "m": 16, "n": 17, "p": 19, "q": 20, "r": 21, "s": 22, "t": 23, "u": 24, "v": 25, "w": 26, "x": 27,
#             "y": 28, "z": 29, "上": 82, "下": 81, "左": 80, "右": 79, "f10": 67, "空格": 44, "大写": 57, "ctrl": 224,
#             "alt": 226, "f2": 59, "f3": 60, "f12": 69, "tab": 43, "shift": 225, "rightshift": 229, "rightctrl": 224,
#             "rightalt": 230,
#             "capslock": 57, "esc": 41, "1": 30, "2": 31, "3": 32, "4": 33, "5": 34, "6": 35, "7": 36, "8": 37, "9": 38,
#             "0": 39, "退格": 42
#         }
#         self.设置分辨率()
#
#     def 设置分辨率(self):
#         hDC = win32gui.GetDC(0)
#         width = win32print.GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
#         height = win32print.GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)
#         self.objdll.M_ResolutionUsed(self.hdl, width, height)
#
#     def 点击键盘(self, 按键, 次数=1):
#         self.objdll.M_KeyPress(self.hdl, self.键盘编码[按键], 次数)
#
#     def 按下键盘(self, 按键):
#         self.objdll.M_KeyDown(self.hdl, self.键盘编码[按键])
#
#     def 松开键盘(self, 按键):
#         self.objdll.M_KeyUp(self.hdl, self.键盘编码[按键])
#
#     def 按下鼠标(self, 左右="左"):
#         if 左右 == "右":
#             res = self.objdll.M_RightDown(self.hdl)
#         else:
#             res = self.objdll.M_LeftDown(self.hdl)
#
#     def 松开鼠标(self, 左右="左"):
#         if 左右 == "右":
#             res = self.objdll.M_RightUp(self.hdl)
#         else:
#             res = self.objdll.M_LeftUp(self.hdl)
#
#     # def 单击鼠标(self, 左右="左"):
#     #     if 左右 == "右":
#     #         res = self.objdll.M_RightUp(self.hdl)
#     #     else:
#     #         res = self.objdll.M_LeftUp(self.hdl)
#
#     def 双击鼠标(self, i=1):
#         res = self.objdll.M_LeftDoubleClick(self.hdl, i)
#
#     def 点击鼠标(self, 左右="左", i=1):
#         if 左右 == "右":
#             res = self.objdll.M_RightClick(self.hdl, i)
#         else:
#             res = self.objdll.M_LeftClick(self.hdl, i)
#
#     def 移动鼠标(self, x, y):
#         x = int(x)
#         y = int(y)
#         res = self.objdll.M_MoveTo3(self.hdl, x, y)
#
#     def 滚动鼠标(self, 上下="下"):
#         if 上下 == "上":
#             res = self.objdll.M_MouseWheel(self.hdl, 1)
#         else:
#             res = self.objdll.M_MouseWheel(self.hdl, -1)
#
#     def 随机坐标(self, 坐标, 范围=3):
#         x = 坐标[0] + randint(-范围, 范围)
#         y = 坐标[1] + randint(-范围, 范围)
#         return x, y
#
#     def 精准点击(self, 坐标, 左右="左", 次数=1, 高精度=False):
#         if not 高精度:
#             x, y = self.随机坐标(坐标)
#         else:
#             x = 坐标[0]
#             y = 坐标[1]
#         self.移动鼠标(x, y)
#         self.点击鼠标(左右=左右, i=次数)
#
#     def 范围点击(self, 范围, 左右="左", 次数=1):
#         """
#         :param 范围: 一个矩形的坐标
#         :param 左右: 左 或者 右
#         :param 次数: 鼠标点击次数
#         :return: None
#         """
#         范围类型 = type(范围)
#         if 范围类型 == tuple or 范围类型 == list:
#             if len(范围) == 2:
#                 self.精准点击(范围, 左右, 次数)
#                 return
#         elif 范围类型 == dict:
#             范围 = 范围["矩形"]
#         try:
#             x = randint(int(范围[0]), int(范围[2]))
#             y = randint(int(范围[1]), int(范围[3]))
#             self.移动鼠标(x, y)
#             self.点击鼠标(左右=左右, i=次数)
#         except Exception as e:
#             处理异常("范围点击出错", e)
#
#     def 拖动鼠标(self, 开始坐标, 结束坐标):
#         """
#         :param 开始坐标: 坐标或者范围
#         :param 结束坐标: 坐标或者范围
#         :return:
#         """
#         try:
#             if len(开始坐标) == 4:
#                 开始坐标 = randint(int(开始坐标[0]), int(开始坐标[2])), randint(int(开始坐标[1]), int(开始坐标[3]))
#             if len(结束坐标) == 4:
#                 结束坐标 = randint(int(结束坐标[0]), int(结束坐标[2])), randint(int(结束坐标[1]), int(结束坐标[3]))
#             self.移动鼠标(int(开始坐标[0]), int(开始坐标[1]))
#             self.按下鼠标()
#             self.移动鼠标(int(结束坐标[0]), int(结束坐标[1]))
#             self.松开鼠标()
#         except Exception as e:
#             处理异常("拖动鼠标出错", e)
#
#     def 按键状态(self, 按键):
#         状态 = self.objdll.M_KeyState(self.hdl, self.键盘编码[按键])
#         if 状态 == -1 or 状态 == 0:
#             return False
#         else:
#             return True
#
#     def 松开所有按键(self):
#         res = self.objdll.M_ReleaseAllKey(self.hdl)
#         return res
#
#     def 停止移动(self):
#         self.松开键盘("上")
#         self.松开键盘("下")
#         self.松开键盘("左")
#         self.松开键盘("右")
#
#     def 验证设备标识(self, data):
#         res = self.objdll.M_VerifyUserData(self.hdl, 14, data)
#         # print(res)
#         if res == 0:
#             return True
#         else:
#             return False


class 键盘鼠标类:
    def __init__(self):
        self.窗口位置 = None
        self.按键类型 = "软"
        self.汉子表 = {
            "空格": "space",
            "上": "up",
            "下": "down",
            "左": "left",
            "右": "right",
            "退格": "backspace"
        }
        self.按下的按键 = []

     
    def 设置分辨率(self, x, y):
        pass

     
    def 点击键盘(self, 按键, 次数=1):
        if self.是汉字(按键):
            按键 = self.汉子表[按键]
        if 次数 == 1:
            pydirectinput.press(按键)
        else:
            for i in range(次数):
                pydirectinput.press(按键)

     
    def 按下键盘(self, 按键):
        if self.是汉字(按键):
            按键 = self.汉子表[按键]
        if 按键 not in self.按下的按键:
            self.按下的按键.append(按键)
        pydirectinput.keyDown(按键)

     
    def 松开键盘(self, 按键):
        if self.是汉字(按键):
            按键 = self.汉子表[按键]
        if 按键 in self.按下的按键:
            self.按下的按键.remove(按键)
        pydirectinput.keyUp(按键)

     
    def 按键状态(self, 按键):
        if self.是汉字(按键):
            按键 = self.汉子表[按键]
        return 按键 in self.按下的按键

     
    def 点击鼠标(self, 左右="左"):
        if 左右 == "右":
            pydirectinput.mouseDown(button="right")
            sleep(0.1)
            pydirectinput.mouseUp(button="right")
        else:
            pydirectinput.mouseDown()
            sleep(0.1)
            pydirectinput.mouseUp()

     
    def 精准点击(self, 坐标, 左右="左", 窗口位置=None, 高精度=False):
        if not 高精度:
            x, y = self.随机坐标(坐标[0], 坐标[1])
        else:
            x = 坐标[0]
            y = 坐标[1]
        if 窗口位置 is None:
            窗口位置 = self.窗口位置
        if 窗口位置:
            x += 窗口位置[0]
            y += 窗口位置[1]
        self.移动鼠标(x, y)
        sleep(0.05)
        self.点击鼠标(左右=左右)

     
    def 双击鼠标(self, 左右=None, 延时=0.05):
        if 左右 == "右":
            pydirectinput.mouseDown(button="right")
            pydirectinput.mouseUp(button="right")
            sleep(0.2)
            pydirectinput.mouseDown(button="right")
            pydirectinput.mouseUp(button="right")
        else:
            pydirectinput.mouseDown()
            pydirectinput.mouseUp()
            sleep(0.5)
            pydirectinput.mouseDown()
            pydirectinput.mouseUp()

     
    def 范围点击(self, 范围, 左右="左", 窗口位置=None):
        """
        :param 范围: 一个矩形的坐标
        :param 左右: 左 或者 右
        :param 次数: 鼠标点击次数
        :return: None
        """
        范围类型 = type(范围)
        if 范围类型 == tuple or 范围类型 == list:
            if len(范围) == 2:
                self.精准点击(范围, 左右)
                return
        elif 范围类型 == dict:
            范围 = 范围["矩形"]
        try:
            x = randint(int(范围[0]), int(范围[2]))
            y = randint(int(范围[1]), int(范围[3]))
            if 窗口位置 is None:
                窗口位置 = self.窗口位置
            if 窗口位置:
                x += 窗口位置[0]
                y += 窗口位置[1]
            self.移动鼠标(x, y)
            sleep(0.05)
            self.点击鼠标(左右=左右)
        except Exception as e:
            处理异常("范围点击出错", e)


     
    def 按下鼠标(self):
        pydirectinput.mouseDown(button="left")

     
    def 松开鼠标(self):
        pydirectinput.mouseUp(button="left")

     
    def 鼠标拖动(self, 坐标1, 坐标2, 窗口位置=None, 精准点击=False):
        if type(坐标1) == dict:
            x1 = int(坐标1["result"][0])
            y1 = int(坐标1["result"][1])
        elif type(坐标1) == tuple:
            x1 = int(坐标1[0])
            y1 = int(坐标1[1])
        else:
            print("鼠标拖动出错")
            return
        if 窗口位置 is None:
            窗口位置 = self.窗口位置
        if 窗口位置:
            x1 += 窗口位置[0]
            y1 += 窗口位置[1]
        if not 精准点击:
            x1, y1 = self.随机坐标(x1, y1)

        if type(坐标2) == dict:
            x2 = int(坐标2["result"][0])
            y2 = int(坐标2["result"][1])
        elif type(坐标2) == tuple:
            x2 = int(坐标2[0])
            y2 = int(坐标2[1])
        else:
            print("鼠标拖动出错")
            return
        if 窗口位置:
            x2 += 窗口位置[0]
            y2 += 窗口位置[1]

        if not 精准点击:
            x2, y2 = self.随机坐标(x2, y2)

        self.移动鼠标(x1, y1, 精准点击=精准点击)
        sleep(0.2)
        self.按下鼠标()
        sleep(0.1)
        self.移动鼠标(x2, y2, 精准点击=精准点击)
        sleep(0.1)
        self.松开鼠标()
        sleep(0.2)

     
    def 随机坐标(self, x, y):
        x = x + randint(-3, 3)
        y = y + randint(-3, 3)
        return x, y

     
    def 移动鼠标(self, x, y, 精准点击=False):
        x = int(x)
        y = int(y)
        if 精准点击 is False:
            x, y = self.随机坐标(x, y)
        pydirectinput.moveTo(x, y)

     
    def 是汉字(self, word):
        """判断一个unicode是否是汉字"""
        for ch in word:
            if '\u4e00' <= ch <= '\u9fff':
                return True
        return False


     
    def 获取鼠标位子(self):
        return pydirectinput.position()

     
    def 松开所有按键(self):
        for 按键 in self.按下的按键:
            Thread(
                target=self.松开键盘,
                args=(按键,)
            ).start()
        self.按下的按键.clear()

     
    def 停止移动(self):
        self.松开键盘("上")
        self.松开键盘("下")
        self.松开键盘("左")
        self.松开键盘("右")

键鼠 = 键盘鼠标类()


class 软件类:
    def __init__(self, 软件名, 软件句柄=None, 模拟器=False):
        print(f"当前加载的软件是：{软件名}")
        self.软件名 = 软件名

        if 软件句柄 is None:
            软件句柄 = self.获取软件句柄()
            if 模拟器:
                软件句柄 = self.获取模拟器操作句柄(软件句柄)
        print(软件句柄)
        self.软件句柄 = 软件句柄
        self.窗口位置 = self.获取窗口坐标()

     
    def 获取模拟器操作句柄(self, 软件句柄):
        return 获取模拟器操作句柄(软件句柄)[0]

     
    def 截图(self) -> object:
        try:
            return 截图(self.窗口位置)
        except Exception as e:
            处理异常("截图出错", e)

     
    def 后台截图(self):
        return 后台截图(self.软件句柄)

     
    def 设置窗口大小和位置(self, 大小=(0, 0), 位置=(0, 0)):
        win32gui.MoveWindow(self.软件句柄, 位置[0], 位置[1], 大小[0], 大小[1], True)
        # win32gui.SetWindowPos(self.软件句柄, win32con.HWND_TOPMOST, 位置[0], 位置[1], 大小[0], 大小[1], 0)

     
    def 保存截图(self, 位置=None, 名字=""):
        try:
            self.设置前台()
            sleep(0.5)
            图片 = self.截图()
            # 图片 = cv2.cvtColor(图片, cv2.COLOR_BGR2GRAY)
            # ret, 图片 = cv2.threshold(src=图片,  # 要二值化的图片
            #                          thresh=100,  # 全局阈值
            #                          maxval=255,  # 大于全局阈值后设定的值
            #                          type=cv2.THRESH_BINARY)  # 设定的二值化类型，

            if 位置 and not os.path.isdir(位置):
                # 创建文件夹
                os.makedirs(位置)

            t = strftime('%m-%d %H %M %S', localtime(time()))
            # cv2.imwrite(r"YC_img\%s_%s.png" % (t, text), IMG)
            if 位置 is None:
                位置 = f"{t}_{名字}.png"
            else:
                位置 = f"{位置}/{t}_{名字}.png"

            cv2.imencode('.png', 图片)[1].tofile(位置)
            print(f"截图以保存到：{位置}")
        except Exception as e:
            处理异常("保存截图出错", e)

     
    def 后台截图并保存(self, 位置=None, 名字=""):
        try:
            图片 = self.后台截图()
            # 图片 = cv2.cvtColor(图片, cv2.COLOR_BGR2GRAY)
            # ret, 图片 = cv2.threshold(src=图片,  # 要二值化的图片
            #                          thresh=100,  # 全局阈值
            #                          maxval=255,  # 大于全局阈值后设定的值
            #                          type=cv2.THRESH_BINARY)  # 设定的二值化类型，

            if 位置 and not os.path.isdir(位置):
                # 创建文件夹
                os.makedirs(位置)

            t = strftime('%m-%d %H %M %S', localtime(time()))
            # cv2.imwrite(r"YC_img\%s_%s.png" % (t, text), IMG)
            if 位置 is None:
                位置 = f"{t}_{名字}.png"
            else:
                位置 = f"{位置}/{t}_{名字}.png"

            cv2.imencode('.png', 图片)[1].tofile(位置)
            print(f"截图以保存到：{位置}")
        except Exception as e:
            处理异常("保存截图出错", e)

     
    def 获取软件句柄(self):
        try:
            win32gui.EnumWindows(get_all_hwnd, 0)
            for h, t in hwnd_map.items():
                if t == self.软件名:
                    return h
        except Exception as e:
            处理异常("获取软件句柄出错", e)

     
    def 获取窗口坐标(self):
        try:
            window_rect = win32gui.GetWindowRect(self.软件句柄)
            return window_rect
        except Exception as e:
            return None
            处理异常("获取软件窗口坐标出错", e)

     
    def 设置前台(self):
        try:
            try:
                shell = win32com.client.Dispatch("WScript.Shell")
                shell.SendKeys('%')
                win32gui.SetForegroundWindow(self.软件句柄)
            except Exception as e:
                try:
                    win32gui.SetForegroundWindow(self.软件句柄)
                except Exception as e:
                    shell = win32com.client.Dispatch("WScript.Shell")
                    shell.SendKeys('%')
                    win32gui.SetForegroundWindow(self.软件句柄)
        except Exception as e:
            处理异常("设置前台出错", e)

     
    def 是否在前台(self):
        try:
            window = win32gui.GetForegroundWindow()
            _, pid = win32process.GetWindowThreadProcessId(window)
            _, process_id = win32process.GetWindowThreadProcessId(self.软件句柄)
            if pid == process_id:
                return True
            else:
                return False
        except Exception as e:
            处理异常("判断是否在前台出错", e)


class 工具类:
    def __init__(self):
        print("初始化工具箱")
        self.桌面分辨率 = self.获取屏幕分辨率()

     
    def 通过名字获取软件句柄(self, 软件名):
        try:
            句柄list = []
            win32gui.EnumWindows(get_all_hwnd, 0)
            for h, t in hwnd_map.items():
                if t:
                    if t == 软件名:
                        句柄list.append(h)

            return 句柄list
        except Exception as e:
            处理异常("获取软件句柄错误", e)

     
    def 获取所有进程句柄和名字(self):
        try:
            句柄list = []
            win32gui.EnumWindows(get_all_hwnd, 0)
            for h, t in hwnd_map.items():
                句柄list.append((h, t))
            return 句柄list
        except Exception as e:
            处理异常("获取软件句柄和名字错误", e)


    def 获取屏幕分辨率(self):
        try:
            hDC = win32gui.GetDC(0)
            width = win32print.GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
            height = win32print.GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)
            return 0, 0, width, height
        except Exception as e:
            处理异常("获取屏幕分辨率出错", e)

     
    def 截取桌面(self):
        try:
            return 截图(self.桌面分辨率)
        except Exception as e:
            处理异常("截图桌面出错", e)

     
    def 等待(self, 秒):
        try:
            sleep(秒)
        except Exception as e:
            处理异常("等待出错", e)

     
    def 时间戳(self):
        try:
            return time()
        except Exception as e:
            处理异常("获取时间戳出错", e)

     
    def 获取字符时间(self, 时间戳=None):
        try:
            if 时间戳 is None:
                时间戳 = time()
            # 把时间戳转换为格式化时间
            time_struct = localtime(时间戳)  # 首先把时间戳转换为结构化时间
            time_format = strftime("%Y-%m-%d %H:%M:%S", time_struct)  # 把结构化时间转换为格式化时间
            return time_format
        except Exception as e:
            处理异常("获取字符时间出错", e)

     
    def 打印(self, 文字):
        try:
            return print(文字)
        except Exception as e:
            处理异常("打印出错", e)

     
    def 保存截图(self, 图片, 位置=None, 名字=""):
        try:
            if 位置 and not os.path.isdir(位置):
                # 创建文件夹
                os.makedirs(位置)

            t = strftime('%m-%d %H %M %S', localtime(time()))
            # cv2.imwrite(r"YC_img\%s_%s.png" % (t, text), IMG)
            if 位置 is None:
                位置 = f"{t}_{名字}.png"
            else:
                位置 = f"{位置}/{t}_{名字}.png"

            cv2.imencode('.png', 图片)[1].tofile(位置)
            print(f"截图以保存到：{位置}")
        except Exception as e:
            处理异常("保存截图出错", e)

     
    def 开启按键监控(self, 监控函数):
        keyboard.Listener(on_release=监控函数).start()


class 多线程():
    def __init__(self, 线程名, 参数):
        self.线程 = Thread(
            target=线程名,
            args=参数
        )

    def 是否存活(self):
        try:
            return self.线程.is_alive()
        except Exception as e:
            return False

    def 等待(self):
        return self.线程.join()

    def 开始(self):
        return self.线程.start()




工具 = 工具类()
if __name__ == '__main__':
    获取所有窗口()

    #
    # # 模拟器 = 软件("雷电模拟器母机")
    # # 模拟器.设置窗口大小和位置(大小=(int(1280 + 42), int(720 + 36)))
    # 模拟器.设置窗口大小和位置(大小=(int(1280/5), int(720/5)))
    # img = 模拟器.后台截图()
    # 后台键鼠 = 后台键鼠(模拟器.软件句柄)
    # 图像处理.展示图片(img)
    # 后台键鼠.范围点击((637, 537))

